using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace WindowsGame3
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        // This is basically your screen.
        GraphicsDeviceManager graphics;
        // This is probably for loading textures/sprites. Like in ucigame...
        SpriteBatch spriteBatch;
        // This is the field that you manipulate when you want to draw stuff on the screen.
        GraphicsDevice device;
        // This affects lighting and color effects.
        Effect effect;
        // This is an array containing all of the vertices for our triangles.
        VertexPositionColorNormal[] vertices;
        // This affects where your camera is.
        Matrix viewMatrix;
        // This affects your field of vision... I think...
        Matrix projectionMatrix;
        // This affects the angle for the camera movement.
        private float angle = 0f;
        // This is the array that contains all of the locations for the indices in the triangles.
        int[] indices;
        // How big the terrain is.
        private int terrainWidth = 4;
        private int terrainHeight = 3;
        // This array contains all of the data for the triangles relating to our heightmap.
        private float[,] heightData;
        // These two things allow the environment to be draw only once instead of every frame.
        VertexBuffer myVertexBuffer;
        IndexBuffer myIndexBuffer;

        // I wouldn't mess with this unless it was absolutely necessary.
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        // This constructs the lighting vertices.
        public struct VertexPositionColorNormal
        {
            // The positions, colors, and the normalizations of the triangle vertices.
            public Vector3 Position;
            public Color Color;
            public Vector3 Normal;

            // I'm guessing that we won't have to mess with this too much since
            // it is a structure that we can make stuff with. But I may be wrong...
            public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
            (
                new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0),
                new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
            );
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            // This is where you decide the screen size, whether or not it is fullscreen,
            // and what the title will be.
            base.Initialize();
            graphics.PreferredBackBufferWidth = 500;
            graphics.PreferredBackBufferHeight = 500;
            graphics.IsFullScreen = false;
            graphics.ApplyChanges();

            Window.Title = "3-D Terrain!";
        }

        // This optimizes the rendering by doing it only once through copying everything to the buffers
        // into the computer's memory, and then loading everything from there. 
        // I'm guessing we won't have to mess with this either.
        private void CopyToBuffers()
        {
            myVertexBuffer = new VertexBuffer(device, VertexPositionColorNormal.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
            myVertexBuffer.SetData(vertices);
            myIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly);
            myIndexBuffer.SetData(indices);
        }

        // This calculates all of the normals in our array of vertices. The normals are used in lighting, and
        // some calcualations need to be done on the vertices and indices to make it look natural.
        // Again, I'm guessing we don't need to mess with this.
        private void CalculateNormals()
        {
            for (int i = 0; i < vertices.Length; i++)
            {
                vertices[i].Normal = new Vector3(0, 0, 0);
            }
            for (int i = 0; i < indices.Length / 3; i++)
            {
                int index1 = indices[i * 3];
                int index2 = indices[i * 3 + 1];
                int index3 = indices[i * 3 + 2];

                Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
                Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
                Vector3 normal = Vector3.Cross(side1, side2);

                vertices[index1].Normal += normal;
                vertices[index2].Normal += normal;
                vertices[index3].Normal += normal;
            }
            for (int i = 0; i < vertices.Length; i++)
            {
                vertices[i].Normal.Normalize();
            }
        }

        // This sets up our 3-dimensional data, by taking the data from the heightmap.
        private void LoadHeightData(Texture2D heightMap)
        {
            // This is obviously the height and width of the map.
            terrainWidth = heightMap.Width;
            terrainHeight = heightMap.Height;

            // this messes with the colors according to the height of the map.
            Color[] heightMapColors = new Color[terrainWidth * terrainHeight];
            heightMap.GetData(heightMapColors);

            // This changes the terrain to the color specified.
            heightData = new float[terrainWidth, terrainHeight];
            for (int x = 0; x < terrainWidth; x++)
                for (int y = 0; y < terrainHeight; y++)
                    heightData[x, y] = heightMapColors[x + y * terrainWidth].R / 5.0f;
        }

        // This sets up all of the vertices and colors them.
        private void SetUpVertices()
        {
            float minHeight = float.MaxValue;
            float maxHeight = float.MinValue;
            
            // go through all of the terrain, and color it according to the heightmap.
            for (int x = 0; x < terrainWidth; x++)
            {
                for (int y = 0; y < terrainHeight; y++)
                {
                    if (heightData[x, y] < minHeight)
                        minHeight = heightData[x, y];
                    if (heightData[x, y] > maxHeight)
                        maxHeight = heightData[x, y];
                }
            }

            // This is where we actually choose the colors for the different heights of the map.
            // I will be messing with this, because we don't want mountains to be red.
            vertices = vertices = new VertexPositionColorNormal[terrainWidth * terrainHeight];
            for (int x = 0; x < terrainWidth; x++)
            {
                for (int y = 0; y < terrainHeight; y++)
                {
                    vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y);

                    if (heightData[x, y] < minHeight + (maxHeight - minHeight) / 4)
                        vertices[x + y * terrainWidth].Color = Color.Beige;
                    else if (heightData[x, y] < minHeight + (maxHeight - minHeight) * 2 / 4)
                        vertices[x + y * terrainWidth].Color = Color.Chartreuse;
                    else if (heightData[x, y] < minHeight + (maxHeight - minHeight) * 3 / 4)
                        vertices[x + y * terrainWidth].Color = Color.Gainsboro;
                    else
                        vertices[x + y * terrainWidth].Color = Color.DarkOrchid;
                    // snow possibilities: gainsboro, 
                }
            }
        }

        // This sets up the indices for the triangle vertices. Not sure how this works, or why it is here,
        // but I don't think we need to mess with it.
        private void SetUpIndices()
        {
            indices = new int[(terrainWidth - 1) * (terrainHeight - 1) * 6];
            int counter = 0;
            for (int y = 0; y < terrainHeight - 1; y++)
            {
                for (int x = 0; x < terrainWidth - 1; x++)
                {
                    int lowerLeft = x + y * terrainWidth;
                    int lowerRight = (x + 1) + y * terrainWidth;
                    int topLeft = x + (y + 1) * terrainWidth;
                    int topRight = (x + 1) + (y + 1) * terrainWidth;

                    indices[counter++] = topLeft;
                    indices[counter++] = lowerRight;
                    indices[counter++] = lowerLeft;

                    indices[counter++] = topLeft;
                    indices[counter++] = topRight;
                    indices[counter++] = lowerRight;
                }
            }
        }

        // This sets up the camera, and chooses a position to create it at.
        // We should experiment with the position for the camera.
        private void SetUpCamera()
        {
            viewMatrix = Matrix.CreateLookAt(new Vector3(60, 80, -80), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 1.0f, 300.0f);
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        // This loads the content silly goose.
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            // we make a new graphics device.
            device = graphics.GraphicsDevice;
            // this loads a file for the different effects. We should probably actually figure out what it does.
            effect = Content.Load<Effect>("effects");
            // This is where you load the bitmap. THIS IS IMPORTANT.
            Texture2D heightMap = Content.Load<Texture2D>("heightmap"); LoadHeightData(heightMap);
            // This is where we call all of our methods that load the 
            // indices, camera, vertices, heightmap, normals, and buffers.
            LoadHeightData(heightMap);
            SetUpVertices();
            SetUpCamera();
            SetUpIndices();
            CalculateNormals();
            CopyToBuffers();
            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        // I didn't use this so I don't think we will need to...
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        // This is where we will mess with the camera controls. Try editing the different values and see what they do.
        // Also go to where angle is calculated and see if you can make it pan.
        protected override void Update(GameTime gameTime)
        {
            KeyboardState keyState = Keyboard.GetState();
            if (keyState.IsKeyDown(Keys.A))
                angle += 0.05f;
            if (keyState.IsKeyDown(Keys.D))
                angle -= 0.05f;
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            // this does the same thing as the clear function in ucigame.
            device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);

            // this allows the camera to see everything.
            RasterizerState rs = new RasterizerState();
            rs.CullMode = CullMode.None;
            device.RasterizerState = rs;

            // this creates the camera and sets up the different effects, lighting directions, etc.
            Matrix worldMatrix = Matrix.CreateTranslation(-terrainWidth / 2.0f, 0, terrainHeight / 2.0f)
                * Matrix.CreateRotationY(angle);
            effect.CurrentTechnique = effect.Techniques["Colored"];
            effect.Parameters["xView"].SetValue(viewMatrix);
            effect.Parameters["xProjection"].SetValue(projectionMatrix);
            effect.Parameters["xWorld"].SetValue(worldMatrix);
            Vector3 lightDirection = new Vector3(1.0f, -1.0f, -1.0f);
            lightDirection.Normalize();
            effect.Parameters["xLightDirection"].SetValue(lightDirection);
            effect.Parameters["xAmbient"].SetValue(0.1f);
            effect.Parameters["xEnableLighting"].SetValue(true);

            // This is where you draw EVERYTHING.
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                device.Indices = myIndexBuffer;
                device.SetVertexBuffer(myVertexBuffer);
                device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, indices.Length / 3);
               // Matrix worldMatrix = Matrix.CreateTranslation(-20.0f / 3.0f, -10.0f / 3.0f, 0)
                 //   * Matrix.CreateFromAxisAngle(rotAxis, angle);
            }
            // TODO: Add your drawing code here

            base.Draw(gameTime);
        }
    }
}
